<?php
/**
 * @purpose
 * @Author: Madou
 * @Time  : 2022/7/29 16:23
 */

namespace App\Trip\Train\Order\Impl\SnapTaskLowLevel;

use App\Jobs\Train\Snap\ProcessSnapTaskExpired;
use App\Jobs\Train\Snap\ProcessSnapTaskLowLevel;
use App\Jobs\Train\Snap\ProcessUnlockWaitingConfirmTask;
use App\Models\Setting\Setting;
use App\Models\Task\SnapTasks\Repositories\Interfaces\SnapTaskRepositoryInterface;
use App\Models\Task\SnapTasks\Repositories\SnapTaskTrip;
use App\Models\Task\SnapTasks\Repositories\SnapTaskTrips;
use App\Models\Task\SnapTasks\SnapTask;
use App\Models\Train\TrainOrders\Repositories\Interfaces\TrainOrdersRepositoryInterface;
use App\Notifications\Train\TrainOrder\SnapConfirmNotify;
use App\Trip\Orders\Traits\ThrowErrorMessageTrait;
use App\Trip\Train\Order\Impl\PiaoChiTrainBookForSnapImpl;
use App\Trip\Train\Order\Interfaces\SnapTaskLowLevelSnap\SnapTaskLowLevelSnapInterface;
use App\Trip\Train\Providers\PiaoChi\TrainQuery;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Throwable;

class SnapTaskLowLevelImpl implements SnapTaskLowLevelSnapInterface
{
    use ThrowErrorMessageTrait;

    private string                         $task_id;
    private string                         $action;
    private SnapTaskRepositoryInterface    $taskRepo;
    private SnapTask                       $task_info;
    private string                         $action_cache_key;
    private string                         $action_redis_key;
    private string                         $action_redis_key_trips;
    private SnapTaskTrips                  $taskTrips;
    private TrainOrdersRepositoryInterface $trainOrderRepo;

    /**
     * @throws Exception
     */
    public function __construct(string $task_id, string $action)
    {
        $this->taskRepo       = app(SnapTaskRepositoryInterface::class);
        $this->trainOrderRepo = app(TrainOrdersRepositoryInterface::class);
        $this->task_info      = $this->taskRepo->findOneByTaskId($task_id);
        $this->task_id        = $task_id;
        $this->action         = $action;
        $this->taskTrips      = new SnapTaskTrips($this->task_info);
        $this->init();
    }

    /**
     * @throws Exception
     */
    private function init()
    {
        $this->action_cache_key = "SnapTaskLowLevelActionCacheKey:{$this->action}:{$this->task_id}";
        $this->action_redis_key = "SnapTaskLowLevelActionRedisKey:{$this->task_id}";
        if (Cache::has($this->action_redis_key)) {
            Log::error("{$this->action_cache_key},操作太快");
            throw new Exception("请不要重复操作");
        }
        $last_action = $this->getCurrentAction();
        if ($this->task_info->isPaidHighLevel()) {
            Log::error("{$this->action_cache_key},高速抢票不匀速此操作");
            throw new Exception("高速抢票不匀速此操作");
        }
        // 缓存至 任务结束
        $stop_at = $this->taskRepo->getStopAt($this->task_info);
        if (Carbon::parse($stop_at)->lessThan(Carbon::now())) {
            ProcessSnapTaskExpired::dispatch($this->task_info);
            throw new Exception("任务已过期,无法执行操作");
        }
        $seconds = $this->taskTrips->getCacheTTL();
        Cache::put($this->action_cache_key, $this->action, $seconds);
        Redis::hmset($this->action_redis_key,
            'action', $this->action,
            'last_action', $last_action
        );
        Log::info("{$this->action_cache_key},当前操作:{$this->action},上一次操作:{$last_action}");
    }

    private function getCurrentAction(): string
    {
        return Redis::hget($this->action_redis_key, 'action') ?: '';
    }

    public function trips()
    {
        return $this->taskTrips;
    }

    public function getCurrentTripIndex(): string
    {
        return Redis::hget($this->action_redis_key, 'trip_index') ?: "";
    }

    /**
     * @throws Exception
     */
    public function start(): bool
    {
        $base_str = "任务开始抢票:{$this->task_id}";
        Log::info("{$base_str}");
        if ($this->task_info->getTaskState() != SnapTask::TASK_STATE_SNAPPING) {
            Log::error("{$base_str},当前任务状态无法抢票,{$this->task_info->getTaskState()}");
            return false;
        }

        if ($this->task_info->getSnapState() != SnapTask::SNAP_STATE_STARTED) {
            Log::error("{$base_str},当前任务抢票状态无法抢票,{$this->task_info->getSnapState()}");
            return false;
        }


        Cache::put($this->action_cache_key, 1);
        /**
         * @var SnapTaskTrip $trip
         */
        $trips           = $this->taskTrips->getTaskTrips();
        $query_res       = [];
        $query           = new TrainQuery();
        $passenger_count = $this->task_info->getTrainOrderInfo()->passengers()->count();
        $chains          = [];
        $book_day        = (new Setting())->getTrainBookDay();
        foreach ($trips as $trip) {
            $train_date = $trip->getTrainDate();
            Log::info("{$base_str},子行程刷票中:日期{$train_date}");
            //车次的忽略超过最大购票日期
            if (Carbon::parse($train_date)->greaterThan(Carbon::today()->addDays($book_day))) {
                Log::info("{$base_str},子行程刷票:日期{$train_date},跳过超过最大购票日期的行程");
                continue;
            }

            // 行程过期的跳过
            if (Carbon::parse($train_date)->lessThan(Carbon::today())) {
                Log::info("{$base_str},子行程刷票:日期{$train_date},行程已过期,跳过");
                continue;
            }


            $from_station_code = $trip->getFromStationCode();
            $to_station_code   = $trip->getToStationCode();
            $qk                = "{$train_date}|{$from_station_code}|{$to_station_code}";
            if (!isset($query_res[$qk])) {
                $param = [
                    "from_station"      => $trip->getFromStationName(),
                    "from_station_code" => $from_station_code,
                    "to_station"        => $trip->getToStationName(),
                    "to_station_code"   => $to_station_code,
                    "train_date"        => $train_date,
                ];
                try {
                    Log::info("{$base_str},子行程刷票,查询余票:{$qk}");
                    $query_res[$qk] = $query->queryTicket($param, true);
                    Log::info("{$base_str},子行程刷票,查询余票:{$qk},结果:".collect($query_res[$qk]["train_numbers"] ?? [])
                            ->map(function ($n) {
                                return $n['seats'] ?? '';
                            })
                            ->toJson(64 | 256));
                } catch (Exception $e) {
                    Log::info("{$base_str},子行程刷票,查询余票:{$qk},失败跳过,等待下次");
                    continue;
                }
            }
            $seat_name  = $trip->getSeatName();
            $train_code = $trip->getTrainCode();
            $qr         = $query_res[$qk];
            $train_info = collect($qr["train_numbers"])->firstWhere('train_code', $train_code);
            $seat       = collect($train_info['seats'] ?? [])
                ->map(fn($v) => collect($v)->except(['list', 'select_seats'])->toArray())
                ->firstWhere('seat_name', $seat_name);

            $trip_stock = $seat['seat_inventory'] ?? -1;
            if ($this->task_info->isAutoTicket()) {
                // 开启自动出票
                if ($trip_stock > 0 && $trip_stock >= $passenger_count - 1) {
                    Log::info("{$base_str},子行程刷票,查询余票:{$qk},当前库存:{$trip_stock},已开启自动出票,行程加入任务链");
                    $chains[] = new ProcessSnapTaskLowLevel($this->task_info, $trip);
                }
            } else {
                if ($trip_stock >= $passenger_count) {
                    // 未开启自动出票
                    Log::info("{$base_str},子行程刷票,查询余票:{$qk},当前库存:{$trip_stock},未开启自动出票,更新抢票状态为待确认,并更新订单状态");
                    Redis::hmset($this->action_redis_key,
                        'trip_index', $trip->getTaskTripIndex(),
                        'trip_confirm_expired_at', Carbon::now()->addMinutes(SnapTask::MANUAL_TICKET_CONFIRM_MINUTES)
                    );
                    DB::transaction(function () {
                        // 设置当前任务的状态为待确认,抢票状态为有票
                        $this->taskRepo->updateTask($this->task_info, [
                            'task_state'    => SnapTask::TASK_STATE_WAITING_CONFIRM,
                            'snap_state'    => SnapTask::SNAP_STATE_HAS_TICKET,
                            'has_ticket_at' => Carbon::now()->toDateTimeString(), // 有票的时间
                        ]);
                    });
                    $order_info = $this->task_info->getTrainOrderInfo();
                    $order_info?->notify(new SnapConfirmNotify($order_info));
                    ProcessUnlockWaitingConfirmTask::dispatch($this->task_info)->delay(Carbon::now()->addMinutes(SnapTask::MANUAL_TICKET_CONFIRM_MINUTES));
                    return true;
                }
            }
        }
        if (!empty($chains)) {
            Log::info("{$base_str},任务链不为空,准备处理....");
            Bus::chain($chains)->catch(function (Throwable $e) use ($base_str) {
                Log::error("{$base_str},任务链异常:".$e->getMessage());
            })->dispatch();
            Log::info("{$base_str},任务链不为空,处理中....");
        } else {
            Log::info("{$base_str},任务链为空,等待下一次刷票...");
        }


        return true;
    }

    /**
     * @throws Exception
     */
    public function stop(): bool
    {
        $base_str   = "任务暂停抢票:{$this->task_id}";
        $order_info = $this->task_info->getTrainOrderInfo();
        // 如果有订单 id
        if ($order_info->getOutOrderId()) {
            $this->throwErrorMessage("正在出单中请稍后");
        }


        if ($this->task_info->getTaskState() != SnapTask::TASK_STATE_SNAPPING) {
            Log::error("{$base_str},当前任务状态无法暂停{$this->task_info->getTaskState()}");
            $this->throwErrorMessage("当前任务状态无法暂停");
        }

        if (!in_array($this->task_info->getSnapState(), [SnapTask::SNAP_STATE_STARTED, SnapTask::SNAP_STATE_SUSPEND,])) {
            Log::error("{$base_str},当前抢票状态无法暂停,{$this->task_info->getSnapState()}");
            $this->throwErrorMessage("当前抢票状态无法暂停");
        }

        // 已暂停 跳过
        if ($this->task_info->getSnapState() == SnapTask::SNAP_STATE_SUSPEND) {
            return true;
        }

        // 先暂停任务
        return $this->taskRepo->updateTask($this->task_info, [
            'snap_state' => SnapTask::SNAP_STATE_SUSPEND,
        ]);
    }

    /**
     * 出票
     *
     * @throws Exception
     */
    public function ticket(SnapTaskTrip $trip)
    {
        if ($this->task_info->isPaidHighLevel()) {
            throw new Exception("当前类型无法出票");
        }

        // 判断是否过期 如果没有开启自动抢票
        if (!$this->task_info->isAutoTicket()) {
            $expired_at = $this->currentTripConfirmTime();
            if (!$expired_at || Carbon::now()->greaterThan($expired_at)) {
                throw new Exception("已过期");
            }
        }


        $order_info = $this->task_info->getTrainOrderInfo();
        // 出票并更新状态任务
        $PiaoChiTrainBookForSnapImpl = new PiaoChiTrainBookForSnapImpl($this->task_info, $this->task_info->getTrainOrderInfo(), $trip);
        return $PiaoChiTrainBookForSnapImpl->book();
    }

    private function currentTripConfirmTime(): string
    {
        return Redis::hget($this->action_redis_key, 'trip_confirm_expired_at');
    }

    public function getStatus(): int
    {
        return $this->task_info->getSnapState();
    }
}